#include "pch.h"
#include "SurfaceReconstruction.h"
#include "FilterPipeConfig.h"
#include "Filter.h"
#include "Pipe.h"
#include <thread>
#include <mutex>
using namespace AutomatedSurfaceReconstruction;

namespace AutomatedSurfaceReconstruction
{
	std::string OutputPath;
	long long ExpectedMeshNum;
	std::counting_semaphore<100> OutputSemaphore(0);
	std::string LogFilePath = "SurfaceReconstructionLogFile.txt";
	std::ofstream WriteLogFile(LogFilePath, std::ios::app);
}

CSurfaceReconstruction::CSurfaceReconstruction(QWidget *vParent)
	: QMainWindow(vParent)
{
	hiveEventLogger::hiveOutputEvent("Event description: Registered signal type...", "SurfaceReconstruction", "SurfaceReconstruction.cpp", 22, hiveEventLogger::EEventLevel::EVENT_LEVEL_INFO);
	qRegisterMetaType<std::string>("std::string");
	qRegisterMetaType<std::vector<string>>("std::vector<std::string>");
	qRegisterMetaType<std::unordered_map<std::string, PCLMeshType::Ptr>>("std::unordered_map<std::string, PCLMeshType::Ptr>");
	qRegisterMetaType<std::unordered_map<std::string, PCLPointSetType::Ptr>>("std::unordered_map<std::string, PCLPointSetType::Ptr>");
	qRegisterMetaType<PCLPointSetType::Ptr>("PCLPointSetType::Ptr");
	qRegisterMetaType<CGALPointSetType>("CGALPointSetType");
	qRegisterMetaType<PCLMeshType::Ptr>("PCLMeshType::Ptr");
	qRegisterMetaType<std::unordered_map<std::string, std::pair<CGALPointSetType, CGALPointSetType>>>("std::unordered_map<std::string, std::pair<CGALPointSetType, CGALPointSetType>>");
	qRegisterMetaType<std::vector<std::pair<std::pair<std::string, CGALPointSetType>, CGALPointSetType>>>("std::vector<std::pair<std::pair<std::string, CGALPointSetType>, CGALPointSetType>>");
}

CSurfaceReconstruction::~CSurfaceReconstruction()
{
	hiveEventLogger::hiveOutputEvent("Event description: Release allocated memory...", "SurfaceReconstruction", "SurfaceReconstruction.cpp", 32, hiveEventLogger::EEventLevel::EVENT_LEVEL_INFO);
	delete m_pLoadPointCloud;
	delete m_pLoadMesh;
	delete m_pFilterPipe;
	__clearListItem();
}

void CSurfaceReconstruction::__clearListItem()
{
	__clearFilePath2PointCloud();
	__clearFilePath2Mesh();
	int ItemsNum = m_UI.LogInfoListWidget->count();
	for (int i = 0; i < ItemsNum; i++)
	{
		QListWidgetItem* Item = m_UI.LogInfoListWidget->takeItem(0);
		delete Item;
	}
}
void CSurfaceReconstruction::init()
{
	hiveEventLogger::hiveOutputEvent("Event description: Init System...", "SurfaceReconstruction", "SurfaceReconstruction.cpp", 53, hiveEventLogger::EEventLevel::EVENT_LEVEL_INFO);
	m_UI.setupUi(this);
	m_pCode = QTextCodec::codecForName("GB2312");

	m_pLoadPointCloud = new CPointCloud();
	m_pLoadPointCloud->moveToThread(&m_LoadPointCloudThread);
	m_pLoadMesh = new CMesh();
	m_pLoadMesh->moveToThread(&m_LoadMeshThread);
	m_pFilterPipe = new CExecFilterPipe;
	m_pFilterPipe->moveToThread(&m_FilterPipeThread);

	__initVTKWidget();
	__connectSignals();

	std::ofstream ClearLogFile(LogFilePath, std::ios::trunc);
	ClearLogFile.close();
	m_ReadLogFileSeek = 0;
}

void CSurfaceReconstruction::__initVTKWidget()
{
	m_pViewer = make_shared<pcl::visualization::PCLVisualizer>("Viewer", false);
	m_UI.DisplayWidget->SetRenderWindow(m_pViewer->getRenderWindow());
	m_pViewer->setupInteractor(m_UI.DisplayWidget->GetInteractor(), m_UI.DisplayWidget->GetRenderWindow());
}

void CSurfaceReconstruction::__connectSignals()
{
	hiveEventLogger::hiveOutputEvent("Event description: Connect Signals to SLOT functions...", "SurfaceReconstruction", "SurfaceReconstruction.cpp", 83, hiveEventLogger::EEventLevel::EVENT_LEVEL_INFO);
	QObject::connect(m_UI.ActionOpen, SIGNAL(triggered()), this, SLOT(onActionOpen()));
	QObject::connect(m_UI.ActionExit, SIGNAL(triggered()), this, SLOT(onActionExit()));
	QObject::connect(m_UI.ActionSurfaceReconstruct, SIGNAL(triggered()), this, SLOT(onActionSurfaceReconstruct()));
	QObject::connect(m_UI.PointCloudListWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(onActionClickPointCloudListItem(QListWidgetItem*)));
	QObject::connect(m_UI.PointCloudListWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onActionRightClickPointCloudItem(QPoint)));
	QObject::connect(m_UI.MeshListWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(onActionClickMeshListItem(QListWidgetItem*)));
	QObject::connect(m_UI.MeshListWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onActionRightClickMeshItem(QPoint)));
	QObject::connect(this, &CSurfaceReconstruction::StartLoadingPointCloud, this, &CSurfaceReconstruction::onActionSetButtonFalse);
	QObject::connect(this, &CSurfaceReconstruction::StartLoadingPointCloud, m_pLoadPointCloud, &CPointCloud::onActionLoadPointCloud);
	QObject::connect(m_pLoadPointCloud, &CPointCloud::FinishLoadPointCloud, this, &CSurfaceReconstruction::onActionLoadOnePointCloud);
	QObject::connect(m_pLoadPointCloud, &CPointCloud::FinishLoadAllPointCloud, this, &CSurfaceReconstruction::onActionLoadAllPointCloud);
	QObject::connect(m_pLoadPointCloud, &CPointCloud::FinishLoadAllPointCloud, this, &CSurfaceReconstruction::onActionSetButtonTure);
	QObject::connect(this, &CSurfaceReconstruction::StartLoadingMesh, m_pLoadMesh, &CMesh::onActionLoadMesh);
	QObject::connect(m_pLoadMesh, &CMesh::FinishLoadMesh, this, &CSurfaceReconstruction::onActionLoadOneMesh);
	QObject::connect(m_pLoadMesh, &CMesh::FinishLoadAllMesh, this, &CSurfaceReconstruction::onActionLoadAllMesh);
	QObject::connect(this, &CSurfaceReconstruction::StartFilterPipe, this, &CSurfaceReconstruction::onActionSetButtonFalse);
	QObject::connect(this, &CSurfaceReconstruction::StartFilterPipe, m_pFilterPipe, &CExecFilterPipe::execFilterPipe);
	QObject::connect(m_pFilterPipe, &CExecFilterPipe::resultReady, this, &CSurfaceReconstruction::onActionReceiveResult);
	QObject::connect(m_pFilterPipe, &CExecFilterPipe::resultReady, this, &CSurfaceReconstruction::onActionSetButtonTure);
	QObject::connect(m_pFilterPipe, &CExecFilterPipe::ReconstructOneMesh, this, &CSurfaceReconstruction::onActionReconstructOneMesh);
}

void CSurfaceReconstruction::__clearFilePath2PointCloud()
{
	int ItemsNum = m_UI.PointCloudListWidget->count();
	for (int i = 0; i < ItemsNum; i++)
	{
		QListWidgetItem* Item = m_UI.PointCloudListWidget->takeItem(0);
		m_FilePath2PointCloud.at(Item->text().toStdString()).reset();
		m_FilePath2PointCloud.erase(Item->text().toStdString());
		m_SimpfiedPointCloudAndDXFPointCloud.erase(Item->text().toStdString());
		delete Item;
	}
	m_pViewer->removeAllShapes();
	m_pViewer->removeAllPointClouds();
	m_pViewer->resetCamera();
	m_pViewer->updateCamera();
}

void CSurfaceReconstruction::__clearFilePath2Mesh()
{
	int ItemsNum = m_UI.MeshListWidget->count();
	for (int i = 0; i < ItemsNum; i++)
	{
		QListWidgetItem* item = m_UI.MeshListWidget->takeItem(0);
		m_FilePath2Mesh.at(item->text().toStdString()).reset();
		m_FilePath2Mesh.erase(item->text().toStdString());
		delete item;
	}
	m_pViewer->removeAllShapes();
	m_pViewer->removeAllPointClouds();
	m_pViewer->resetCamera();
	m_pViewer->updateCamera();
}

void CSurfaceReconstruction::__updateVTKWidget(const PCLMeshType::Ptr& vMesh)
{
	m_pViewer->removeAllShapes();
	m_pViewer->removeAllPointClouds();
	m_pViewer->addPolygonMesh(*vMesh, "DisplayFile");
	m_pViewer->setRepresentationToWireframeForAllActors();
	m_pViewer->resetCamera();
	m_pViewer->updateCamera();

}

void CSurfaceReconstruction::__updateVTKWidget(const PCLPointSetType::Ptr& vPointCloud)
{
	m_pViewer->removeAllShapes();
	m_pViewer->removeAllPointClouds();
	m_pViewer->addPointCloud(vPointCloud, "DisplayFile");
	m_pViewer->resetCamera();
	m_pViewer->updateCamera();
}

void CSurfaceReconstruction::__addItem2ListWidget(const std::string& vItemText, QListWidget* vopListWidget)
{
	QListWidgetItem* Item = new QListWidgetItem(QString::fromStdString(vItemText));
	vopListWidget->addItem(Item);
	vopListWidget->scrollToBottom();
}

std::string CSurfaceReconstruction::__generateLogInfo(const std::string& vLogInfo)
{
	return generateTime() + "        " + vLogInfo;
}

long long CSurfaceReconstruction::__convertQString2LongLong(QString vData)
{
	stringstream TempStream;
	TempStream << vData.toStdString();
	long long LongLongData;
	TempStream >> LongLongData;
	return LongLongData;
}

void CSurfaceReconstruction::__ReadLogFile(std::string vLogFilePath)
{
	std::ifstream ReadLogFile(vLogFilePath);
	std::string RowContent;

	while (m_ReadLogFileFlag)
	{
		ReadLogFile.seekg(0, std::ios::end);
		int FileLength = ReadLogFile.tellg();
		if (m_ReadLogFileSeek >= FileLength)continue;
		ReadLogFile.seekg(m_ReadLogFileSeek, std::ios::beg);

		std::getline(ReadLogFile, RowContent);
		__addItem2ListWidget(RowContent, m_UI.LogInfoListWidget);

		int NowPos = ReadLogFile.tellg();
		if (NowPos == EOF)m_ReadLogFileSeek = FileLength + 1;
		else m_ReadLogFileSeek = NowPos;
	}
}

void CSurfaceReconstruction::__getDXFSet(std::vector<std::string>& vioLasFilePathSet, std::vector<std::string>& voDXFFilePathSet)
{
	for(auto LasFilePath = vioLasFilePathSet.begin();LasFilePath!=vioLasFilePathSet.end();)
	{
		std::string DXFFilePath = LasFilePath->substr(0, LasFilePath->find_last_of('.')) + ".dxf";
		QFileInfo FileInfo(QString::fromStdString(DXFFilePath));

		if (FileInfo.isFile())
		{
			voDXFFilePathSet.push_back(DXFFilePath);
			LasFilePath++;
		}
		else
		{
			LasFilePath = vioLasFilePathSet.erase(LasFilePath);
			QMessageBox::warning(this, tr(NULL), tr("The point cloud doesn't have matched DXF file "), QMessageBox::Yes);
			__addItem2ListWidget(__generateLogInfo("["+*LasFilePath + "] cann't find matched DXF file."), m_UI.LogInfoListWidget);
		}
	}
}

void CSurfaceReconstruction::onActionOpen()
{
	QStringList FilePathList = QFileDialog::getOpenFileNames(this, tr("Open PointCloud or Mesh"), tr("."), tr("Files(*.las);; Files(*.obj))"));
	if (FilePathList.empty())return;
	QString FileSuffix;
	std::vector<std::string> FilePathStringSet;
	std::vector<std::string> DXFPathStringSet;

	foreach(QString FilePathQString, FilePathList)
	{
		std::string FilePathString = m_pCode->fromUnicode(FilePathQString).data();
		FilePathStringSet.push_back(FilePathString);
	}

	FileSuffix = QFileInfo(FilePathList[0]).suffix();

	if (FileSuffix == QString("las")) 
	{
		__getDXFSet(FilePathStringSet, DXFPathStringSet);
		if (!FilePathStringSet.empty())
		{
			m_LoadPointCloudThread.start();
			emit StartLoadingPointCloud(FilePathStringSet, m_FilePath2PointCloud);
			__addItem2ListWidget(__generateLogInfo("Start to import files..."), m_UI.LogInfoListWidget);
		}
	}
	else 
	{
		m_LoadMeshThread.start();
		emit StartLoadingMesh(FilePathStringSet, m_FilePath2Mesh);
		__addItem2ListWidget(__generateLogInfo("Start to import files..."), m_UI.LogInfoListWidget);
	}
}

void CSurfaceReconstruction::onActionSurfaceReconstruct()
{
	if (m_FilePath2PointCloud.empty())
	{
		QMessageBox::warning(this, tr(NULL), tr("Please import the pointclouds. "), QMessageBox::Yes);
	}
	else
	{
		bool IsSimplifyOK;
		QString text = QInputDialog::getText(NULL, NULL, "Please input the count of reconstruction faces.", QLineEdit::Normal, "100000", &IsSimplifyOK);
		if (IsSimplifyOK) {
			AutomatedSurfaceReconstruction::ExpectedMeshNum = __convertQString2LongLong(text);
			QString SavePath = QFileDialog::getSaveFileName(this, tr(NULL), "output", tr(""));
			if (SavePath.isEmpty())return;
			AutomatedSurfaceReconstruction::OutputPath = m_pCode->fromUnicode(SavePath).data();

			m_ReadLogFileFlag = true;
			auto ReadFunc = std::bind(&CSurfaceReconstruction::__ReadLogFile, this, LogFilePath);
			std::thread ReadFile(ReadFunc);
			ReadFile.detach();

			m_FilterPipeThread.start();
			__addItem2ListWidget(__generateLogInfo("Start to Reconstruct files..."), m_UI.LogInfoListWidget);
			emit StartFilterPipe(m_SimpfiedPointCloudAndDXFPointCloud);
			
		}
	}
}

void CSurfaceReconstruction::onActionExit()
{
	hiveEventLogger::hiveOutputEvent("Event description: Exit...", "SurfaceReconstruction", "SurfaceReconstruction.cpp", 321, hiveEventLogger::EEventLevel::EVENT_LEVEL_INFO);
	QApplication* Application;
	Application->quit();
}

void CSurfaceReconstruction::onActionClickPointCloudListItem(QListWidgetItem* item)
{
	__updateVTKWidget(m_FilePath2PointCloud.at(item->text().toStdString()));
	__addHighLightPointCloud(m_SimpfiedPointCloudAndDXFPointCloud.at(item->text().toStdString()).second);
}

void CSurfaceReconstruction::onActionRightClickPointCloudItem(QPoint pos)
{
	QListWidgetItem* ClickedItem = m_UI.PointCloudListWidget->itemAt(pos);
	if (ClickedItem == NULL)return;

	QMenu* Menu = new QMenu(this);
	QAction* DeleteItem = new QAction(tr("Delete"), this);
	QAction* ClearItem = new QAction(tr("Clear"), this);
	Menu->addAction(DeleteItem);
	Menu->addAction(ClearItem);
	QObject::connect(DeleteItem, SIGNAL(triggered()), this, SLOT(onActionDeletePointCloudItem()));
	QObject::connect(ClearItem, SIGNAL(triggered()), this, SLOT(onActionClearPointCloudItem()));
	Menu->exec(QCursor::pos());
	delete Menu;
	delete DeleteItem;
	delete ClearItem;
}

void CSurfaceReconstruction::onActionDeletePointCloudItem()
{
	QListWidgetItem* ClickedItem = m_UI.PointCloudListWidget->currentItem();
	__addItem2ListWidget(__generateLogInfo("Delete file: " + (ClickedItem->text()).toStdString()), m_UI.LogInfoListWidget);
	m_FilePath2PointCloud.at(ClickedItem->text().toStdString()).reset();
	m_FilePath2PointCloud.erase(ClickedItem->text().toStdString());
	m_SimpfiedPointCloudAndDXFPointCloud.erase(ClickedItem->text().toStdString());
	delete ClickedItem;
	if (m_FilePath2PointCloud.size()) 
	{ 
		__updateVTKWidget(m_FilePath2PointCloud.begin()->second); 
		__addHighLightPointCloud(m_SimpfiedPointCloudAndDXFPointCloud.begin()->second.second);
	}
	else { __clearFilePath2PointCloud(); }
}

void CSurfaceReconstruction::onActionClearPointCloudItem()
{
	__clearFilePath2PointCloud();
	__addItem2ListWidget(__generateLogInfo("Clear pointcloud files."), m_UI.LogInfoListWidget);
}

void CSurfaceReconstruction::onActionClickMeshListItem(QListWidgetItem* item)
{
	__updateVTKWidget(m_FilePath2Mesh.at(item->text().toStdString()));
}

void CSurfaceReconstruction::onActionRightClickMeshItem(QPoint pos)
{
	QListWidgetItem* ClickedItem = m_UI.MeshListWidget->itemAt(pos);
	if (ClickedItem == NULL)return;

	QMenu* Menu = new QMenu(this);
	QAction* DeleteItem = new QAction(tr("Delete"), this);
	QAction* ClearItem = new QAction(tr("Clear"), this);
	Menu->addAction(DeleteItem);
	Menu->addAction(ClearItem);
	QObject::connect(DeleteItem, SIGNAL(triggered()), this, SLOT(onActionDeleteMeshItem()));
	QObject::connect(ClearItem, SIGNAL(triggered()), this, SLOT(onActionClearMeshItem()));
	Menu->exec(QCursor::pos());

	delete Menu;
	delete DeleteItem;
	delete ClearItem;
}

void CSurfaceReconstruction::onActionDeleteMeshItem()
{
	QListWidgetItem* ClickedItem = m_UI.MeshListWidget->currentItem();
	__addItem2ListWidget(__generateLogInfo("Delete file: " + (ClickedItem->text()).toStdString()), m_UI.LogInfoListWidget);
	m_FilePath2Mesh.at(ClickedItem->text().toStdString()).reset();
	m_FilePath2Mesh.erase(ClickedItem->text().toStdString());
	delete ClickedItem;
	if (m_FilePath2Mesh.size()) 
	{
		__updateVTKWidget(m_FilePath2Mesh.begin()->second);

	}
	else { __clearFilePath2Mesh(); }
}

void CSurfaceReconstruction::onActionClearMeshItem()
{
	__clearFilePath2Mesh();
	__addItem2ListWidget(__generateLogInfo("Clear mesh files."), m_UI.LogInfoListWidget);
}

void CSurfaceReconstruction::onActionLoadOnePointCloud(std::string vFilePath, PCLPointSetType::Ptr vOriginPointCloud, CGALPointSetType vSimpfiedPointCloud, CGALPointSetType vDXFPointCloud)
{
	__addItem2ListWidget(vFilePath, m_UI.PointCloudListWidget);
	__addItem2ListWidget(__generateLogInfo("Import file: [" + vFilePath + "]"), m_UI.LogInfoListWidget);

	m_FilePath2PointCloud.emplace(std::make_pair(vFilePath,vOriginPointCloud));
	m_SimpfiedPointCloudAndDXFPointCloud.emplace(std::make_pair(vFilePath, std::make_pair(vSimpfiedPointCloud, vDXFPointCloud)));
}

void CSurfaceReconstruction::onActionLoadAllPointCloud()
{
	m_LoadPointCloudThread.quit();
	__updateVTKWidget(m_FilePath2PointCloud.begin()->second);
	
	__addHighLightPointCloud(m_SimpfiedPointCloudAndDXFPointCloud.begin()->second.second);
}

void CSurfaceReconstruction::__addHighLightPointCloud(const CGALPointSetType& vCGALPointCloud)
{
	PCLPointSetType::Ptr AddPCLPointSet(new PCLPointSetType);
	convertPointCloudFromCGAL2PCL(vCGALPointCloud, AddPCLPointSet);

	pcl::visualization::PointCloudColorHandlerCustom<pcl::PointXYZRGB> HighlightColor(AddPCLPointSet, 255, 0, 0);
	std::string PointCloudId = m_SimpfiedPointCloudAndDXFPointCloud.begin()->first;
	m_pViewer->addPointCloud(AddPCLPointSet, HighlightColor, PointCloudId);
	m_pViewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 5, PointCloudId);
	m_pViewer->updateCamera();
}

void CSurfaceReconstruction::onActionLoadOneMesh(std::string vFilePath,PCLMeshType::Ptr vMesh)
{
	__addItem2ListWidget(vFilePath, m_UI.MeshListWidget);
	__addItem2ListWidget(__generateLogInfo("Import file: [" + vFilePath + "]"), m_UI.LogInfoListWidget);
	m_FilePath2Mesh.emplace(std::make_pair(vFilePath, vMesh));
}

void CSurfaceReconstruction::onActionLoadAllMesh()
{
	m_LoadMeshThread.quit();
	__updateVTKWidget(m_FilePath2Mesh.begin()->second);
}

void CSurfaceReconstruction::onActionSetButtonFalse()
{
	m_UI.ActionOpen->setEnabled(false);
	m_UI.ActionSurfaceReconstruct->setEnabled(false);
	m_UI.PointCloudListWidget->setContextMenuPolicy(Qt::NoContextMenu);
}

void CSurfaceReconstruction::onActionSetButtonTure()
{
	m_UI.ActionOpen->setEnabled(true);
	m_UI.ActionSurfaceReconstruct->setEnabled(true);
	m_UI.PointCloudListWidget->setContextMenuPolicy(Qt::CustomContextMenu);
}

void CSurfaceReconstruction::onActionReconstructOneMesh(std::string vMeshPath)
{
	std::vector<std::string> FilePathStringSet;
	FilePathStringSet.push_back(vMeshPath);
	m_LoadMeshThread.start();
	emit StartLoadingMesh(FilePathStringSet, m_FilePath2Mesh);
}

void CSurfaceReconstruction::onActionReceiveResult()
{
	m_ReadLogFileFlag = false;
	m_FilterPipeThread.quit();
	QMessageBox::information(this, NULL, "Finish SurfaceReconstruction.");
}